﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Xml.Linq;
using AjaxControlToolkit;
using Formsy.Config;
using Formsy.Data.Interfaces;
using Formsy.Data.Services;
using Formsy.Common;
using Formsy.Providers.CustomField;
using Formsy.Providers.Email;
using Formsy.Providers.Regionalization;
using Formsy.Providers.DataStore;
using Formsy.Providers.FileSystem;
using Formsy.Providers.Validation;
using Formsy.Providers.PrePopulate;
using System.Web.Compilation;
using Formsy.Core.Resources;
using System.Transactions;

namespace Formsy.Core
{
    /// <summary>
    /// The main class for creating forms. 
    /// Parses XML and creates the controls.
    /// 
    /// </summary>
    public class FormEngine : WebControl
    {
        #region Public Properties

        // defined in markup
        public string FormDefinitionPath { get; set; }

        // defined in markup
        public string DictionaryPath { get; set; }

        // Error message to be displayed on submit failure
        public string Error { get; set; }

        // This is a hack to get the client id to be what I want it to be :)
        public override string ClientID
        {
            get
            {
                return "Formsy";
            }
        }

        #endregion



        #region Protected Properties

        // does all the fiddly work to make the controls
        protected ControlBuilder m_ControlFactory;

        // the form name
        protected string m_FormName;

        // page to redirect to on successful submit
        protected string m_SuccessPage;

        protected bool m_Updatable;

        // used when no fieldset name is given
        protected int fieldSetCount = 0;

        // collections to allow retrieval of values on submit
        protected List<FormField> m_FormFields;
        //protected List<FormField> m_BinaryFormFields;

        // providers
        protected string m_DataStoreProvider;
        protected string m_FileSystemProvider;
        protected string m_ValidationProvider;
        protected string m_EmailProvider;
        protected string m_PrePopulateProvider;

        protected XElement m_PrePopulateParams;
        protected XElement m_DataStoreParams;
        protected XElement m_FileSystemParams;
        protected XElement m_EmailParams;
        protected XElement m_ValidationParams;
        
        // email addresses
        protected bool m_EmailEnabled;
        protected string m_RecipientAddresses = string.Empty;
        protected string m_SenderAddress = string.Empty;

        #endregion



        #region OnInit, OnLoad, OnPreRender

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            // initialize collections
            m_FormFields = new List<FormField>();
            //   m_BinaryFormFields = new List<FormField>();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            //if (Page.IsPostBack)
            //    return;

            // create the controls
            EnsureChildControls();
        }


        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            //if (Page.IsPostBack)
            //    return;

            Regionalize();
            PrePopulate();
        }

        #endregion



        #region PrePopulate
        /// <summary>
        /// Calls prepopulate provider to prepopulate fields in the form.
        /// </summary>
        protected void PrePopulate()
        {
            //  pre-populate the form
            PrePopulateProviderManager.Providers[m_PrePopulateProvider].PrePopulate(m_FormFields, m_PrePopulateParams);
        }

        #endregion



        #region Regionalize

        /// <summary>
        /// Replaces labels with regionalized ones.
        /// </summary>
        protected void Regionalize()
        {
            if (string.IsNullOrEmpty(DictionaryPath))
                return;

            // get dictionary xml
            XDocument doc = HttpDataService.Instance.FormsyService.LoadXDocument(DictionaryPath);
            XElement xml = doc.Descendants("Dictionary").FirstOrDefault();
            string provider = xml.Attribute("Provider").Value;

            RegionalizationProviderManager.Providers[provider].RegionalizeLabels(m_FormFields, xml);
            RegionalizationProviderManager.Providers[provider].RegionalizeValidatiionMessages(m_FormFields, xml);
            RegionalizationProviderManager.Providers[provider].RegionalizeRegex(m_FormFields, xml);
        }

        #endregion



        #region CreateChildControls

        protected override void CreateChildControls()
        {
            m_ControlFactory = new ControlBuilder();

            // get xml form definition
            XDocument doc = HttpDataService.Instance.FormsyService.LoadXDocument(FormDefinitionPath);

            XElement xmlForm = doc.Descendants("Form").FirstOrDefault();

            LoadFormAttributes(xmlForm);

            // use default style sheet?
            string useDefaultStyle = xmlForm.GetAttributeOrDefault("UseDefaultStyle") != null ? xmlForm.GetAttributeOrDefault("UseDefaultStyle") : bool.FalseString;

            if (bool.Parse(useDefaultStyle) == true)
            {
                m_ControlFactory.AddResource(EmbededResource.FormDefaultStyle, ResourceType.Style);
            }

            // create containing div
            HtmlGenericControl div = m_ControlFactory.CreateHtmlControl(ControlType.Div, m_FormName, string.Empty);

            // create validation summary
            string enabled = xmlForm.Attribute("ValidationSummary").Value;
            ValidationSummary vs = m_ControlFactory.CreateValidationSummary(m_FormName, enabled);
            div.Controls.Add(vs);

            // create fieldsets
            CreateFieldSets(xmlForm, div);

            // create hidden field for entry updating
            if (m_Updatable)
                m_ControlFactory.CreateEntryUpdateField(div, m_FormFields);

            // add submit button
            Button button = m_ControlFactory.CreateButton("Submit", m_FormName, "Submit");
            button.Click += new EventHandler(ButtonClicked);
            div.Controls.Add(button);
            this.Controls.Add(div);

            // TODO: implement other types of buttons!...eg linkimage
        }

        #endregion



        #region CreateFieldSets

        protected void CreateFieldSets(XElement xmlForm, HtmlGenericControl div)
        {
            var xmlFieldSets = xmlForm.Descendants("FieldSet");
            foreach (var xmlFieldSet in xmlFieldSets)
            {
                HtmlGenericControl fieldSet;

                // get fieldset name
                string fieldSetName = GetFieldSetName(xmlFieldSet);
                if (fieldSetName != null)
                {
                    fieldSet = m_ControlFactory.CreateHtmlControl("fieldset", fieldSetName, ClientStrings.Default.FieldSetClass);

                    // create fieldset title
                    HtmlGenericControl fieldSetTitle = m_ControlFactory.CreateHtmlControl(ControlType.H3, fieldSetName, string.Empty);
                    LiteralControl l = new LiteralControl(fieldSetName);
                    fieldSetTitle.Controls.Add(l);
                    fieldSet.Controls.Add(fieldSetTitle);
                }
                else // create fieldset with no title
                    fieldSet = m_ControlFactory.CreateHtmlControl("fieldset", GetFieldSetName(null), ClientStrings.Default.FieldSetClass);

                CreateFields(xmlFieldSet, fieldSet);
                div.Controls.Add(fieldSet);
                fieldSetCount++;
            }
        }

        #endregion



        #region CreateFields

        protected HtmlGenericControl CreateFields(XElement xmlFieldSet, HtmlGenericControl fieldSet)
        {
            var fields = xmlFieldSet.Descendants("Field");
            foreach (var field in fields)
            {
                // create containing div
                string name = field.GetName();
                HtmlGenericControl div = m_ControlFactory.CreateHtmlControl(ControlType.Div, name, ClientStrings.Default.FieldClass);

                // add field type as class 
                string fieldType = field.GetFieldType();
                m_ControlFactory.AddTypeAsClass(div, fieldType);

                // create control
                if (ControlType.IsNonStandardField(fieldType))
                    CreateCustomField(field, div, fieldSet);
                else
                    CreateStandardField(field, div, fieldSet);


            }
            return fieldSet;
        }

        #endregion



        #region CreateCustomField

        protected HtmlGenericControl CreateCustomField(XElement field, HtmlGenericControl div, HtmlGenericControl fieldSet)
        {
            string inputType = field.GetFieldType();

            //var t = BuildManager.GetType("THETYPEINCONFIG");
            //var obj = Activator.CreateInstance(t);
            //if (!(obj is Control))
            //{
            //    throw new ArgumentException("SDFSD");
            //}

            //if (obj is RequiredFieldValidator)
            //{

            //}

            Control control = null;
            if (inputType == ControlType.PasswordPair)
            {
                // create two divs with a password field in each
                m_ControlFactory.CreatePasswordPair(field, div, m_FormName, m_FormFields, fieldSet);
            }
            if (inputType == ControlType.EmailPair)
            {
                // create two divs with a password field in each
                m_ControlFactory.CreateEmailPair(field, div, m_FormName, m_FormFields, fieldSet);
            }

            else if (inputType == ControlType.FileUpload)
            {
                control = m_ControlFactory.CreateFileUpload(field, div, m_FormFields, m_FormName, EmbededResource.FormClearFileUploadFunction, fieldSet);
            }
            else if (inputType == ControlType.DatePicker)
            {
                control = m_ControlFactory.CreateDatePicker(field, div, this, m_FormFields, m_FormName, fieldSet);
            }
            else if (inputType == ControlType.Literal)
            {
                // create a literal inside a span
                string name = field.GetName();
                //HtmlGenericControl lit = m_ControlFactory.CreateLiteral(field, name, m_FormFields);
                HtmlGenericControl lit = m_ControlFactory.CreateLiteral(name, field.GetText());
                div.Controls.Add(lit);
                control = div;
                m_FormFields.Add(new FormField(lit.ID, name, lit, null, ControlType.Literal, true));
            }
            else if (inputType == ControlType.CustomField)
            {
                var xmlProvider = field.Descendants("Provider").SingleOrDefault();
                string provider = xmlProvider.Attribute("Name").Value;
                control = CustomFieldProviderManager.Providers[provider].CreateCustomField(field, div, fieldSet, m_FormFields);
            }

            if (control != null)
                fieldSet.Controls.Add(control);
            return div;
        }

        #endregion



        #region CreateStandardField

        protected HtmlGenericControl CreateStandardField(XElement field, HtmlGenericControl div, HtmlGenericControl fieldSet)
        {
            string inputType = field.GetFieldType();

            Control inputControl = null;
            if (inputType == ControlType.TextBox)
                inputControl = m_ControlFactory.CreateTextBox(field.GetName(), string.Empty);
            else if (inputType == ControlType.CheckBox)
                inputControl = m_ControlFactory.CreateCheckBox(field.GetName());
            else if (inputType == ControlType.TextArea)
                inputControl = m_ControlFactory.CreateTextArea(field.GetName());
            else if (inputType == ControlType.RadioButton)
                inputControl = m_ControlFactory.CreateRadioButton(field.GetName());
            else if (inputType == ControlType.HiddenField)
                inputControl = m_ControlFactory.CreateHiddenField(field.GetName(), field.GetValue());
            else if (inputType == ControlType.DropDownList)
                inputControl = m_ControlFactory.CreateDropDownList(field);
            else if (inputType == ControlType.RadioButtonList)
                inputControl = m_ControlFactory.CreateRadioButtonList(field);
            else if (inputType == ControlType.CheckBoxList)
                inputControl = m_ControlFactory.CreateCheckBoxList(field);
            else if (inputType == ControlType.PasswordField)
                inputControl = m_ControlFactory.CreatePasswordField(field.GetName(), string.Empty);
            else
                throw new Exception(inputType + " not supported.");

            // label
            Label label = new Label();
            if (inputType != ControlType.HiddenField)
            {
                label = m_ControlFactory.CreateLabel(field.GetName(), inputControl.ID, string.Empty);

                // use label attribute in xml if it exists
                var suppliedLabel = field.GetAttributeOrDefault("Label");
                if (suppliedLabel != null)
                    label.Text = suppliedLabel;

                div.Controls.Add(label);
            }
            else
            {
                div.Attributes["style"] = "display:none;";
                //div.Visible = false;
            }
            // add the control to the containing div
            div.Controls.Add(inputControl);

            // add field to m_FormFields when IgnoreData attribute is not true
            var ignoreData = field.GetAttributeOrDefault("IgnoreData");
            if (string.IsNullOrEmpty(ignoreData))
                m_FormFields.Add(new FormField(inputControl.ID, label.Text, inputControl, label, field.GetFieldType(), false));
            else
            {
                if (bool.Parse(ignoreData) == false)
                    m_FormFields.Add(new FormField(inputControl.ID, label.Text, inputControl, label, field.GetFieldType(), false));
            }

            // validators
            m_ControlFactory.CreateValidators(field, m_FormName, fieldSet, div, inputControl, m_FormFields, string.Empty);

            // add the div to the fieldset
            fieldSet.Controls.Add(div);
            return div;
        }

        #endregion




        #region LoadFormAttributes

        /// <summary>
        /// Gets main form attributes from xml.
        /// </summary>
        /// <param name="xmlForm"></param>
        protected void LoadFormAttributes(XElement xmlForm)
        {
            // get form name
            m_FormName = xmlForm.GetName();

            // get page to redirect to on successful submit 
            m_SuccessPage = xmlForm.GetAttributeOrDefault("SuccessPage");

            // get updatable
            string s = xmlForm.GetAttributeOrDefault("Updatable");
            m_Updatable = string.IsNullOrEmpty(s) ? false : bool.Parse(s);

            // get providers for this form
            var xmlProviders = xmlForm.Descendants("Providers").SingleOrDefault();
            List<XElement> providers;

            if (xmlProviders != null)
                providers = xmlProviders.Descendants("Provider").ToList();
            else
                providers = new List<XElement>();

            // for storage to custom database
            m_ValidationProvider = GetProvider(providers, "Validation", out m_ValidationParams);

            // for storage to custom database
            m_DataStoreProvider = GetProvider(providers, "DataStore", out m_DataStoreParams);

            // for custom handling of file saving
            m_FileSystemProvider = GetProvider(providers, "FileSystem", out m_FileSystemParams);

            // for custom handling of emails 
            m_PrePopulateProvider = GetProvider(providers, "PrePopulate", out m_PrePopulateParams);

            // for custom handling of emails 
            m_EmailProvider = GetProvider(providers, "Email", out m_EmailParams);

            // get email recipients
            var emailEnabled = xmlForm.GetAttributeOrDefault("EmailEnabled");
            m_EmailEnabled = !string.IsNullOrEmpty(emailEnabled) ? bool.Parse(emailEnabled) : false;

            if (m_EmailEnabled)
            {
                var emailProvider = providers.Where(x => x.GetAttributeOrDefault("Type") == "Email").FirstOrDefault();

                if (emailProvider != null)
                {
                    m_RecipientAddresses = emailProvider.GetAttributeOrDefault("RecipientAddress");
                    m_SenderAddress = emailProvider.GetAttributeOrDefault("SenderAddress");
                }
            }
        }

        #endregion



        #region GetProvider

        /// <summary>
        /// Returns the provider name for the given type, or "StandardProvider".
        /// </summary>
        /// <param name="field">The current xml node.</param>
        /// <param name="name">Name of the provider to get.s</param>
        /// <param name="parameters">An XElement to store the xml so the provider can use it later.</param>
        /// <returns></returns>
        protected string GetProvider(IEnumerable<XElement> field, string name, out XElement parameters)
        {
            var provider = field.Where(x => x.Attribute("Type").Value == name)
                                .FirstOrDefault();
            if (provider != null)
            {
                parameters = provider; // store XElement for provider to use later
                return provider.GetName();
            }
            parameters = null;
            return "StandardProvider";
        }

        #endregion



        #region GetFieldSetName

        /// <summary>
        /// Returns null if the Label attribute is not present.
        /// Note: An empty string for the Label attribute is still returned.
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        protected string GetFieldSetName(XElement x)
        {
            if (x == null)
                return "fieldset" + fieldSetCount;

            if (x.HasAttribute("Name"))
                return x.GetName();
            return null;
        }

        #endregion



        #region GetFormsyFilePath

        protected string GetFormsyFilePath()
        {
            string configPath = Settings.Instance.FormsyAppSettings.FormsyFilePath.Replace('/', '\\');
            string fullPath = HttpContext.Current.Server.MapPath(configPath + "\\" + m_FormName + "\\");

            // ensure folder exists!
            FileSystemProviderManager.Providers[m_FileSystemProvider].CreateFolder(fullPath);

            return fullPath;
        }

        #endregion




        #region ButtonClicked

        /// <summary>
        /// The submit button click.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ButtonClicked(Object sender, EventArgs e)
        {
            Dictionary<string, string> dictionary = GetDictionary();
            List<FormsyFile> binaryFields = GetBinaryFields();

            // custom validation provider 
            bool isValid = ValidationProviderManager.Providers[m_ValidationProvider].Validate(this, m_FormFields, dictionary);

            if (!isValid)
                return;

            // wrap database calls and file system write in transaction
            using (TransactionScope scope = new TransactionScope())
            {
                // insert entry
                IFormsyEntry entry = DataStoreProviderManager.Providers[m_DataStoreProvider].InsertEntry(m_FormName, m_Updatable);

                int entryId;
                if (!m_Updatable)
                    entryId = entry.Id;
                else
                    entryId = GetUpdateEntryId();

                // save the files
                List<FormsyFile> files = FileSystemProviderManager.Providers[m_FileSystemProvider].Save(m_FormName, entryId, binaryFields, GetFormsyFilePath());

                // add file paths to the dictionary
                foreach (var f in files)
                {
                    if (dictionary.ContainsKey(f.FieldName))
                        dictionary[f.FieldName] = f.Filename;
                    else
                        dictionary.Add(f.FieldName, f.Filename);
                }
                
                // insert data entries 
                DataStoreProviderManager.Providers[m_DataStoreProvider].InsertEntryData(entry.FormId, entryId, dictionary, m_Updatable);

                // send form data to email recipient 
                if (m_EmailEnabled)
                    EmailProviderManager.Providers[m_EmailProvider].Send(m_FormName, m_SenderAddress, m_RecipientAddresses, dictionary);
                
                scope.Complete();
                scope.Dispose();
            }
            

            // redirect to success page
            HttpContext.Current.Response.Redirect(m_SuccessPage);
        }

        #endregion




        #region GetFormFields

        /// <summary>
        /// Returns a dictionary of all the form fields except FirstName, LastName, and Email.
        /// <returns></returns>
        public Dictionary<string, string> GetDictionary()
        {
            Dictionary<string, string> dictionary = new Dictionary<string, string>();

            foreach (var item in m_FormFields)
            {
                string value = item.Value;

                //if (!string.IsNullOrEmpty(value) && item.Label != null)
                //if (!string.IsNullOrEmpty(value) && item.FieldName != null)
                //{
                    if (item.Type == ControlType.HiddenField && item.IgnoreData == false)
                        dictionary.Add(item.Control.ID, value);
                    else
                    {
                        if (item.IgnoreData == false)
                            //dictionary.Add(item.Label.Text, value);
                            dictionary.Add(item.FieldName, value);
                    }
                //}
            }
            return dictionary;
        }

        #endregion



        #region GetBinaryFields

        protected List<FormsyFile> GetBinaryFields()
        {
            List<FormsyFile> fields = new List<FormsyFile>();

            foreach (var item in m_FormFields)
            {
                if (item.Type == ControlType.FileUpload)
                {
                    var f = ((FileUpload)item.Control);
                    var value = f.FileBytes;
                    string extension = f.FileName.Split(".".ToCharArray()).LastOrDefault();
                    fields.Add(new FormsyFile(item.Label.Text, value, !string.IsNullOrEmpty(extension) ? extension : string.Empty));
                }
            }
            return fields;
        }

        #endregion



        #region GetUpdateEntryId

        protected int GetUpdateEntryId()
        {
            var f = m_FormFields.Where(x => x.FieldName == "FormsyEntryId").FirstOrDefault();
            string value = ((HiddenField)f.Control).Value;
            return int.Parse(value);
        }

        #endregion

    }
}

